package com.supply.hsa.link.common.server.impl;

import com.fangcang.common.ResponseDTO;
import com.fangcang.common.enums.ResultCodeEnum;
import com.fangcang.common.util.PropertyCopyUtil;
import com.google.common.cache.*;
import com.supply.hsa.link.common.constant.SupplierClass;
import com.supply.hsa.link.common.dto.*;
import com.supply.hsa.link.common.server.SupplyDataMappingServer;
import com.supply.hsa.link.domain.SupplyHotelMappingDO;
import com.supply.hsa.link.domain.SupplyRoomMappingDO;
import com.supply.hsa.link.mapper.PricePlanMappingMapper;
import com.supply.hsa.link.mapper.SupplyHotelMappingMapper;
import com.supply.hsa.link.mapper.SupplyRoomMappingMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import tk.mybatis.mapper.entity.Example;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class SupplyDataMappingServerImpl  implements SupplyDataMappingServer {
    @Autowired
    private SupplyHotelMappingMapper supplyHotelMappingMapper;
    @Autowired
    private SupplyRoomMappingMapper supplyRoomMappingMapper;
    @Autowired
    private PricePlanMappingMapper pricePlanMappingMapper;


    @Override
    public ResponseDTO addHotelMapping(HotelMappingDTO request) {
        ResponseDTO responseDTO = new ResponseDTO();
        try {
            SupplyHotelMappingDO mappingDO = PropertyCopyUtil.transfer(request, SupplyHotelMappingDO.class);

            Example queryExample = new Example(SupplyHotelMappingDO.class);
            Example.Criteria mappingQueryCriteria = queryExample.createCriteria();
            mappingQueryCriteria.andEqualTo("coHotelId",mappingDO.getCoHotelId());
            mappingQueryCriteria.andEqualTo("supplierClass",mappingDO.getSupplierClass());
            mappingQueryCriteria.andEqualTo("isActive",mappingDO.getIsActive());
            List<SupplyHotelMappingDO> mapList = supplyHotelMappingMapper.selectByExample(queryExample);
            if(!CollectionUtils.isEmpty(mapList)){
                mappingDO.setId(mapList.get(0).getId());
                supplyHotelMappingMapper.updateByPrimaryKeySelective(mappingDO);
            }else {
                supplyHotelMappingMapper.insert(mappingDO);
            }
            responseDTO.setResult(ResultCodeEnum.SUCCESS.code);
        }catch (Exception e) {
            log.error("addHotelMapping has error", e);
            responseDTO.setResult(ResultCodeEnum.FAILURE.code);
        }
        return responseDTO;
    }

    @Override
    public ResponseDTO addRoomMapping(RoomMappingDTO request) {
        ResponseDTO responseDTO = new ResponseDTO();
        try {
            SupplyRoomMappingDO mappingDO = PropertyCopyUtil.transfer(request, SupplyRoomMappingDO.class);
            Example queryExample = new Example(SupplyRoomMappingDO.class);
            Example.Criteria mappingQueryCriteria = queryExample.createCriteria();
            mappingQueryCriteria.andEqualTo("coHotelId",mappingDO.getCoHotelId());
            mappingQueryCriteria.andEqualTo("coRoomId",mappingDO.getCoRoomId());
            mappingQueryCriteria.andEqualTo("supplierClass",mappingDO.getSupplierClass());
            mappingQueryCriteria.andEqualTo("isActive",mappingDO.getIsActive());
            List<SupplyRoomMappingDO> mapList = supplyRoomMappingMapper.selectByExample(queryExample);
            if(!CollectionUtils.isEmpty(mapList)){
                mappingDO.setId(mapList.get(0).getId());
                supplyRoomMappingMapper.updateByPrimaryKeySelective(mappingDO);
            }else {
                supplyRoomMappingMapper.insert(mappingDO);
            }
            responseDTO.setResult(ResultCodeEnum.SUCCESS.code);
        }catch (Exception e) {
            log.error("addRoomMapping has error", e);
            responseDTO.setResult(ResultCodeEnum.FAILURE.code);
        }
        return responseDTO;
    }

    @Override
    public ResponseDTO addPricePlanMapping(PricePlanMappingDTO request) {
        ResponseDTO responseDTO = new ResponseDTO();
        try {
            SupplyRoomMappingDO mappingDO = PropertyCopyUtil.transfer(request, SupplyRoomMappingDO.class);
            supplyRoomMappingMapper.insert(mappingDO);
            responseDTO.setResult(ResultCodeEnum.SUCCESS.code);
        }catch (Exception e) {
            log.error("addPricePlanMapping has error", e);
            responseDTO.setResult(ResultCodeEnum.FAILURE.code);
        }
        return responseDTO;
    }

    @Override
    public List<HotelAndRoomMapDTO> querySupplyHotelMappingCache(SupplierClass supplierClass, String coHotelId) {
        if(StringUtils.isBlank(coHotelId) || supplierClass == null){
            return null;
        }

        try {
            return this.hotelMappingWrapperCache.get(new HotelMappingCacheKey(supplierClass.getSupplierClass(), coHotelId));
        } catch (Exception e) {
            //guava cache，值为null时，会抛RuntimeException
            log.error("酒店房型映射缓存异常",e);
            return Collections.emptyList();
        }
    }

    @Override
    public List<PricePlanMappingDTO> queryPricePlanMapping(SupplierClass supplierClass, String supplyCode, String coHotelId, boolean enableMappingCache) {
        if (StringUtils.isBlank(supplyCode) || supplierClass == null
                || StringUtils.isBlank(coHotelId)) {
            return Collections.emptyList();
        }

        if(enableMappingCache){
            try {
                return this.priceplanMappingCache.get(
                        new PricePlanMappingCacheKey(supplierClass.getSupplierClass(), supplyCode, coHotelId));
            } catch (Exception e) {
                //guava cache，值为null时，会抛RuntimeException
                log.info("价格计划缓存映射异常",e);
                return Collections.emptyList();
            }
        }else{
            return queryPricePlanMappingNest(supplierClass.getSupplierClass(), supplyCode, coHotelId);
        }
    }

    private List<PricePlanMappingDTO> queryPricePlanMappingNest(String supplierClass, String supplyCode, String coHotelId){
        PricePlanMappingQueryDTO planMappingQueryNew = new PricePlanMappingQueryDTO();
        planMappingQueryNew.setSupplierClass(supplierClass);
        planMappingQueryNew.setSupplierCode(supplyCode);
        planMappingQueryNew.setSupplyHotelIds(Collections.singletonList(coHotelId));

        PricePlanMappingQueryDTO pricePlanMappingQuery = new PricePlanMappingQueryDTO();
        pricePlanMappingQuery.setSupplierClass(supplierClass);
        pricePlanMappingQuery.setSupplierCode(supplyCode);
        pricePlanMappingQuery.setSupplyHotelIds(Collections.singletonList(coHotelId));

        List<PricePlanMappingDTO> pricePlanMappingList = pricePlanMappingMapper.queryPricePlanMappingList(pricePlanMappingQuery);
        if(CollectionUtils.isEmpty(pricePlanMappingList)){
            return null;//防止cache缓存空集合
        }
        return pricePlanMappingList;
    }

    private LoadingCache<PricePlanMappingCacheKey, List<PricePlanMappingDTO>> priceplanMappingCache =
            CacheBuilder.newBuilder()
                    .expireAfterWrite(24, TimeUnit.HOURS)//假定61分钟内价格计划映射数量没有变化，没有出现新增、删除操作
                    .removalListener(new RemovalListener<PricePlanMappingCacheKey, List<PricePlanMappingDTO>>() {
                        @Override
                        public void onRemoval(
                                RemovalNotification<PricePlanMappingCacheKey, List<PricePlanMappingDTO>> notification) {
                            log.info("",notification.getKey());
                        }
                    })
                    .build(new CacheLoader<PricePlanMappingCacheKey, List<PricePlanMappingDTO>>() {
                        @Override
                        public List<PricePlanMappingDTO> load(PricePlanMappingCacheKey key)
                                throws Exception {
                            return queryPricePlanMappingNest(key.supplierClass, key.supplyCode, key.coHotelId);
                        }
                    });

    private LoadingCache<HotelMappingCacheKey, List<HotelAndRoomMapDTO>> hotelMappingWrapperCache =
            CacheBuilder.newBuilder()
                    //.softValues()
                    // .concurrencyLevel(10)
                    // .recordStats()
                    // .maximumSize(50000)
                    .expireAfterWrite(72, TimeUnit.HOURS)//假定12小时内酒店映射没有发生变化
                    .build(new CacheLoader<HotelMappingCacheKey, List<HotelAndRoomMapDTO>>() {
                        @Override
                        public List<HotelAndRoomMapDTO> load(
                                HotelMappingCacheKey key) throws Exception {
                            return querySupplyHotelMappingNest(key.supplierClass, Collections.singletonList(key.coHotelId));
                        }
                    });

    private List<HotelAndRoomMapDTO> querySupplyHotelMappingNest(String supplierClass, List<String> coHotelIdList){
        QueryHotelAndRoomMapDTO queryHotelAndRoomMap = new QueryHotelAndRoomMapDTO();
        queryHotelAndRoomMap.setSupplierClass(supplierClass);
        queryHotelAndRoomMap.setSupplierHotelIds(coHotelIdList);
        List<HotelAndRoomMapDTO> result = supplyHotelMappingMapper.queryHotelAndRoomMapping(queryHotelAndRoomMap);
        if(CollectionUtils.isEmpty(result)){
            return null;//防止cache缓存空集合
        }
        return result;
    }

    private static class HotelMappingCacheKey{
        String supplierClass;
        String coHotelId;
        HotelMappingCacheKey(String supplierClass, String coHotelId){
            this.supplierClass = supplierClass;
            this.coHotelId = coHotelId;
        }
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((coHotelId == null) ? 0 : coHotelId.hashCode());
            result = prime * result + ((supplierClass == null) ? 0 : supplierClass.hashCode());
            return result;
        }
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            HotelMappingCacheKey other = (HotelMappingCacheKey) obj;
            if (coHotelId == null) {
                if (other.coHotelId != null)
                    return false;
            } else if (!coHotelId.equals(other.coHotelId))
                return false;
            if (supplierClass == null) {
                if (other.supplierClass != null)
                    return false;
            } else if (!supplierClass.equals(other.supplierClass))
                return false;
            return true;
        }
    }

    private static class PricePlanMappingCacheKey{
        String supplierClass;
        String supplyCode;
        String coHotelId;
        PricePlanMappingCacheKey(String supplierClass, String supplyCode, String coHotelId){
            this.supplierClass = supplierClass;
            this.supplyCode = supplyCode;
            this.coHotelId = coHotelId;
        }
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result
                    + ((coHotelId == null) ? 0 : coHotelId.hashCode());
            result = prime * result
                    + ((supplierClass == null) ? 0 : supplierClass.hashCode());
            result = prime * result
                    + ((supplyCode == null) ? 0 : supplyCode.hashCode());
            return result;
        }
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            PricePlanMappingCacheKey other = (PricePlanMappingCacheKey) obj;
            if (coHotelId == null) {
                if (other.coHotelId != null)
                    return false;
            } else if (!coHotelId.equals(other.coHotelId))
                return false;
            if (supplierClass == null) {
                if (other.supplierClass != null)
                    return false;
            } else if (!supplierClass.equals(other.supplierClass))
                return false;
            if (supplyCode == null) {
                if (other.supplyCode != null)
                    return false;
            } else if (!supplyCode.equals(other.supplyCode))
                return false;
            return true;
        }
        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder.append("PricePlanMappingCacheKey [supplierClass=")
                    .append(supplierClass).append(", supplyCode=")
                    .append(supplyCode).append(", coHotelId=")
                    .append(coHotelId).append("]");
            return builder.toString();
        }
    }
}
